/**
 * Class Jsl.Xml
 * Basic Xml document manipulation
 * 
 * @author  Mykhailo Stadnyk <mikhus@gmail.com>
 * @package Jsl
 * @version 1.0
 */
Jsl.Xml = Jsl.extend( Jsl.Observable, function() {

	var
		w      = window,
		d      = document,
		Hp     = HTMLElement.prototype,
		C      = Jsl.Collection,
		props  = [
			'find','appendTo','before','after','clone','empty','text',
			'remove','replace','attr','parents','first','last'
		],
		Sizzle = w.Sizzle
	;

	return {

	xml      : '',
	dom      : null,
	url      : null,
	proxy    : null,
	loaded   : false,
	parsed   : false,
	autoLoad : false,

	/**
	 * Class constructor
	 * 
	 * @param {Object} config
	 */
	constructor : function( config) {
		this.addEvents({
			/**
			 * Fires each time xml document loaded and parsed
			 * 
			 * @param {XMLDocument} dom - DOM XML document
			 */
			load         : true,

			/**
			 * Fires each time after before document parse
			 * 
			 * @param {String} xml - XML text
			 */
			beforeparse  : true,

			/**
			 * Fires each time document parsed
			 * 
			 * @param {XMLDocument} dom - XML DOM object
			 */
			parse        : true
		});

		Jsl.Xml.$super.constructor.call( this, config);
		Jsl.apply( this, config);

		this.autoLoad && this.load();
	},

	/**
	 * Loads an XML document by its url in asynchronous manner
	 * 
	 * @param {String} url           - [Optional] If not specified will be used url defined in constructor's config 
	 * @param {Jsl.Ajax.Proxy} proxy - [Optional] If not specified will be used proxy defined in constructor's config or default Jsl proxy
	 */
	load : function( url, proxy) {
		var proxy = proxy || this.proxy,
			url   = url || this.url,
			self = this
		;

		if (!url)
			throw new Error( 'Jsl.Xml.load(): url is not defined')
		;

		this.url = url;
		if (Jsl.isObject( proxy))
			proxy = new Jsl.Ajax.Proxy( proxy)
		;
		if (!(proxy instanceof Jsl.Ajax.Proxy))
			proxy = null
		;
		this.proxy = proxy;

		this.loaded = this.parsed = false;

		Jsl.Ajax.request({
			url     : url,
			proxy   : proxy,
			type    : 'text',
			success : function( text) {
				self.xml    = text;
				self.loaded = true;
				self.parse();
				self.fireEvent( 'load', self.dom);
			}
		});
	},

	/**
	 * Reloads loaded document
	 */
	reload : function() {
		this.load( this.url);
		return this;
	},

	/**
	 * Parses an XML text to DOM document
	 * 
	 * @param  {String} text - XML text
	 * @return {Jsl.Xml}
	 */
	parse : function( text) {
		if (this.fireEvent( 'beforeparse', text)) {
			var doc, text = text || this.xml;
			if (Jsl.isDefined( window.DOMParser)) {
				var parser = new DOMParser();
				doc = parser.parseFromString( text, 'text/xml');
			} else if (Jsl.isDefined( ActiveXObject)) {
				doc = new ActiveXObject( 'Microsoft.XMLDOM');
				doc.async = false;
				doc.loadXML( text);
			}

			/**
			 * Adding extended DOM functionalities
			 */
			doc.query = function( selector) {
				return new Jsl.Collection(
					Sizzle( selector, this)
				);
			};
			var els = doc.query( '*');
			els && els.each( function( el) {
				for (var i = 0, prop; prop = props[i]; i++)
					el[prop] = Hp[prop]
				;
				el.append = function( _el) {
					if (!Jsl.isElement( _el)) {
						try { _el = doc.createElement( _el); } catch (e) { return null; }
					}
					this.appendChild && this.appendChild( _el);
					return _el;
				};
			});

			if (this.fireEvent( 'parse', doc)) {
				this.dom = doc;
				this.parsed = true;
			}
		}
		return this;
	},

	/**
	 * Returns current document's XML text
	 * 
	 * @return {String}
	 */
	getXml : function() { return this.xml; },

	/**
	 * Returns current document's DOM model
	 * 
	 * @return {XMLDocument}
	 */
	getDom : function() { return this.dom; },

	/**
	 * Runs the specified selector and returns an array of matched DOMElements
	 * 
	 * @param {String} selector
	 */
	query : function( selector) {
		if (this.dom) {
			return this.dom.query( selector);
		}
		return null;
	}

};}());
